Skip to main content

pacenotes_installer\wizard\scene/
fetching.rs

1use windows_sys::Win32::Foundation::{HWND, LPARAM, WPARAM};
2use windows_sys::Win32::UI::Controls as ctl;
3use windows_sys::Win32::UI::WindowsAndMessaging as wm;
4
5/// Scene 4b: fetch what scene 4 ticked into the cache, touching nothing in the
6/// game folder, so the questions come after the transfers. Cancel is the only
7/// button, honoured here because a half-finished download costs nothing; the
8/// page moves on by itself.
9pub unsafe extern "system" fn fetching_page(
10    dialog: HWND,
11    message: u32,
12    wparam: WPARAM,
13    lparam: LPARAM,
14) -> isize {
15    match message {
16        wm::WM_INITDIALOG => {
17            crate::wizard::page::adopt_state(dialog, lparam);
18            super::progress_page::init_progress(dialog);
19            1
20        }
21        crate::worker::WM_WORKER_PROGRESS => {
22            let Some(progress) = crate::worker::take_progress(lparam) else {
23                return 0;
24            };
25            if let Some(state) = crate::wizard::page::state(dialog) {
26                if state.is_current_request(wparam) {
27                    super::progress_page::show_progress(dialog, state, &progress);
28                }
29            }
30            1
31        }
32        crate::worker::WM_WORKER_DONE => {
33            let Some(message) = crate::worker::take(lparam) else {
34                return 0;
35            };
36            if let (Some(state), crate::worker::Message::Downloaded(fetched)) =
37                (crate::wizard::page::state(dialog), message)
38            {
39                if state.is_current_request(wparam) {
40                    finish(dialog, state, fetched);
41                }
42            }
43            1
44        }
45        wm::WM_NOTIFY => match crate::wizard::page::notify_code(lparam) {
46            Some(ctl::PSN_SETACTIVE) => {
47                crate::wizard::page::set_sheet_caption(dialog);
48                crate::wizard::page::set_header(dialog, crate::i18n::IDS_S4B_TITLE);
49                crate::wizard::page::set_buttons(dialog, 0);
50                if let Some(state) = crate::wizard::page::state(dialog) {
51                    super::progress_page::show_log(dialog, state);
52                }
53                start(dialog);
54                crate::wizard::page::reply(dialog, 0)
55            }
56            Some(ctl::PSN_QUERYCANCEL) => super::progress_page::on_cancel(dialog),
57            _ => 0,
58        },
59        _ => 0,
60    }
61}
62
63/// Everything ticked that is not already here, which is the scene 4 selection,
64/// since the scene 5 picks are not made yet.
65unsafe fn start(dialog: HWND) {
66    let Some(state) = crate::wizard::page::state(dialog) else {
67        return;
68    };
69    if state.download_started {
70        return;
71    }
72    state.download_started = true;
73
74    let missing: Vec<pacenotes_shared::PackageInfo> = state
75        .tree
76        .checked_needing_download()
77        .iter()
78        .filter_map(|key| {
79            state
80                .packages
81                .iter()
82                .find(|package| &package.download_path() == key)
83                .cloned()
84        })
85        // The tree's states date from scene 4's first visit, and a selection
86        // sent back here has had a transfer since.
87        .filter(|package| {
88            let cached = state.cache.get(&package.download_path());
89            !cached.is_some_and(|entry| entry.sha256 == package.sha256)
90                || !state.cache.has_payload(&package.sha256)
91        })
92        .collect();
93    if missing.is_empty() {
94        let nothing = crate::i18n::load_string(crate::i18n::IDS_S6_NOTHING);
95        super::progress_page::log_line(dialog, state, &nothing);
96        let done = crate::worker::Fetched::Done {
97            downloaded: 0,
98            bytes: 0,
99            failed: Vec::new(),
100        };
101        finish(dialog, state, done);
102        return;
103    }
104
105    crate::ui::set_text(
106        dialog,
107        crate::wizard::control_ids::IDC_S6_PHASE,
108        crate::i18n::IDS_S6_DOWNLOADING,
109    );
110    crate::ui::enable(
111        crate::wizard::page::sheet(dialog),
112        crate::ui::IDCANCEL,
113        true,
114    );
115    let request = state.next_request();
116    let job = crate::worker::DownloadJob {
117        base_url: state.base_url.clone(),
118        token: state.token(),
119        cache_root: state.cache.root().to_path_buf(),
120        downloads: missing,
121        cancel: std::sync::Arc::clone(&state.cancel),
122    };
123    crate::worker::spawn_reporting(dialog, request, move |reporter| {
124        crate::worker::run_download(job, reporter)
125    });
126}
127
128/// The questions come next, without what never arrived; when nothing did, or
129/// the run was cancelled, it ends at the summary, by page id, since nothing
130/// new could honestly be installed. When a category is left with nothing to
131/// pick from, it is back to scene 4 for another selection.
132unsafe fn finish(
133    dialog: HWND,
134    state: &mut crate::wizard::state::WizardState,
135    fetched: crate::worker::Fetched,
136) {
137    match fetched {
138        crate::worker::Fetched::Done {
139            downloaded,
140            bytes,
141            failed,
142        } => {
143            // Added up, since a selection sent back from here comes through again.
144            state.downloaded += downloaded;
145            state.downloaded_bytes += bytes;
146            if downloaded > 0 {
147                let line = format!(
148                    "downloaded {downloaded} package(s), {}",
149                    pacenotes_installer::size::human(bytes)
150                );
151                super::progress_page::log_line(dialog, state, &line);
152            }
153            // The worker wrote the index through its own handle, and scene 5 and the install read it.
154            state.cache = pacenotes_installer::cache::Cache::open(state.cache.root().to_path_buf());
155            leave_out(dialog, state, failed);
156            if downloaded == 0 && !state.not_downloaded.is_empty() {
157                let nothing = crate::i18n::load_string(crate::i18n::IDS_S7_NONE_DOWNLOADED);
158                end_run_early(dialog, state, crate::worker::Outcome::Failed(nothing));
159                return;
160            }
161            if !super::configuration::has_something_to_pick(dialog, state) {
162                back_to_selection(dialog, state);
163                return;
164            }
165            super::progress_page::finish_page(dialog, crate::i18n::IDS_S4B_DONE, None);
166        }
167        crate::worker::Fetched::Cancelled => {
168            super::progress_page::log_line(dialog, state, "cancelled by the user");
169            end_run_early(dialog, state, crate::worker::Outcome::Cancelled);
170        }
171    }
172}
173
174/// Logs each package that never arrived and unticks it, unless the cache holds
175/// a copy: a failed UPDATE keeps the installed version, which an untick would
176/// have the plan uninstall.
177unsafe fn leave_out(
178    dialog: HWND,
179    state: &mut crate::wizard::state::WizardState,
180    failed: Vec<crate::worker::NotDownloaded>,
181) {
182    for failure in &failed {
183        let line = format!("not downloaded: {} — {}", failure.key, failure.reason);
184        super::progress_page::log_line(dialog, state, &line);
185        if state.cache.get(&failure.key).is_none() {
186            state.tree.uncheck_key(&failure.key);
187        }
188    }
189    state.not_downloaded = failed;
190}
191
192/// Scene 4 again, with what never arrived unticked there, and a fresh transfer
193/// once the user sends a new selection.
194unsafe fn back_to_selection(dialog: HWND, state: &mut crate::wizard::state::WizardState) {
195    super::progress_page::log_line(
196        dialog,
197        state,
198        "nothing to configure yet: back to the selection",
199    );
200    state.download_started = false;
201    super::progress_page::set_progress(dialog, 0);
202    let sheet = crate::wizard::page::sheet(dialog);
203    let selection = crate::wizard::control_ids::IDD_S4_DOWNLOAD;
204    wm::PostMessageW(sheet, ctl::PSM_SETCURSELID, 0, selection as LPARAM);
205}
206
207unsafe fn end_run_early(
208    dialog: HWND,
209    state: &mut crate::wizard::state::WizardState,
210    outcome: crate::worker::Outcome,
211) {
212    state.outcome = Some(outcome);
213    let summary = crate::wizard::control_ids::IDD_S7_SUMMARY;
214    super::progress_page::finish_page(dialog, crate::i18n::IDS_S6_FINISHED, Some(summary));
215}